// LoadCodecs.cpp

#include "StdAfx.h"

#include "LoadCodecs.h"

#ifdef NEW_FOLDER_INTERFACE
#include "Common/String/StringToInt.h"
#endif
#include "Common/MS/PropVariant.h"

#include "Common/COM/ICoder.h"
#include "Common/7Zip/RegisterArc.h"

#ifdef EXTERNAL_CODECS
#include "Common/FileSystem/FileFind.h"
#include "Common/System/DLL.h"
#ifdef NEW_FOLDER_INTERFACE
#include "Common/String/ResourceString.h"
static const UINT kIconTypesResId = 100;
#endif

#ifdef _WIN32
#include "Common/Registry/Registry.h"
#endif

namespace Common
{
	using namespace System;
namespace Archive
{


static CString GetLibraryFolderPrefix()
{
  #ifdef _WIN32
  TCHAR fullPath[MAX_PATH + 1];
  ::GetModuleFileName(_AtlBaseModule.GetModuleInstance(), fullPath, MAX_PATH);
  CString path = fullPath;
  int pos = path.ReverseFind(CHAR_PATH_SEPARATOR);
  return path.Left(pos + 1);
  #else
  return CString(); // FIX IT
  #endif
}

#define kCodecsFolderName TEXT("Codecs")
#define kFormatsFolderName TEXT("Formats")
static const TCHAR *kMainDll = TEXT("7z.dll");

#ifdef _WIN32
static LPCTSTR kRegistryPath = TEXT("Software") STRING_PATH_SEPARATOR TEXT("7-zip");
static LPCTSTR kProgramPathValue = TEXT("Path");
static bool ReadPathFromRegistry(HKEY baseKey, CString &path)
{
  Registry::CKey key;
  if(key.Open(baseKey, kRegistryPath, KEY_READ) == ERROR_SUCCESS)
	if (key.QueryValue(kProgramPathValue, path) == ERROR_SUCCESS)
	{
	  FileSystem::FileName::NormalizeDirPathPrefix(path);
	  return true;
	}
  return false;
}

#endif

CString GetBaseFolderPrefixFromRegistry()
{
  CString moduleFolderPrefix = GetLibraryFolderPrefix();
  #ifdef _WIN32
  if (!FileSystem::Find::DoesFileExist(moduleFolderPrefix + kMainDll) &&
	  !FileSystem::Find::DoesDirExist(moduleFolderPrefix + kCodecsFolderName) &&
	  !FileSystem::Find::DoesDirExist(moduleFolderPrefix + kFormatsFolderName))
  {
	CString path;
	if (ReadPathFromRegistry(HKEY_CURRENT_USER, path))
	  return path;
	if (ReadPathFromRegistry(HKEY_LOCAL_MACHINE, path))
	  return path;
  }
  #endif
  return moduleFolderPrefix;
}

typedef UINT32 (WINAPI *GetNumberOfMethodsFunc)(UINT32 *numMethods);
typedef UINT32 (WINAPI *GetNumberOfFormatsFunc)(UINT32 *numFormats);
typedef UINT32 (WINAPI *GetHandlerPropertyFunc)(PROPID propID, PROPVARIANT *value);
typedef UINT32 (WINAPI *GetHandlerPropertyFunc2)(UINT32 index, PROPID propID, PROPVARIANT *value);
typedef UINT32 (WINAPI *CreateObjectFunc)(const GUID *clsID, const GUID *iid, void **outObject);
typedef UINT32 (WINAPI *SetLargePageModeFunc)();


static HRESULT GetCoderClass(GetMethodPropertyFunc getMethodProperty, UINT32 index,
	PROPID propId, CLSID &clsId, bool &isAssigned)
{
  CComPropVariant prop;
  isAssigned = false;
  HRESULT hRes = getMethodProperty(index, propId, &prop);
  if(S_OK != hRes)
	return hRes;
  if (prop.vt == VT_BSTR)
  {
	isAssigned = true;
	clsId = *(const GUID *)prop.bstrVal;
  }
  else if (prop.vt != VT_EMPTY)
	return E_FAIL;
  return S_OK;
}

HRESULT CCodecs::LoadCodecs()
{
	if(Libs.empty())
		return S_OK;
  CCodecLib &lib = Libs.back();
  lib.GetMethodProperty = (GetMethodPropertyFunc)lib.Lib.GetProcAddress("GetMethodProperty");
  if (lib.GetMethodProperty == NULL)
	return S_OK;

  UINT32 numMethods = 1;
  GetNumberOfMethodsFunc getNumberOfMethodsFunc = (GetNumberOfMethodsFunc)lib.Lib.GetProcAddress("GetNumberOfMethods");
  HRESULT hRes(S_OK);
  if (getNumberOfMethodsFunc != NULL)
  {
	hRes = getNumberOfMethodsFunc(&numMethods);
	if(S_OK != hRes)
		return hRes;
  }

  Codecs.reserve(numMethods);
  for(UINT32 i = 0; i < numMethods; i++)
  {
	CDllCodecInfo info;
	info.LibIndex = Libs.size() - 1;
	info.CodecIndex = i;

	hRes = GetCoderClass(lib.GetMethodProperty, i, MethodPropID::kEncoder, info.Encoder, info.EncoderIsAssigned);
	if(S_OK != hRes)
		return hRes;
	hRes =  GetCoderClass(lib.GetMethodProperty, i, MethodPropID::kDecoder, info.Decoder, info.DecoderIsAssigned);
	if(S_OK != hRes)
		return hRes;

	Codecs.push_back(info);
  }
  return S_OK;
}

static HRESULT ReadProp(
	GetHandlerPropertyFunc getProp,
	GetHandlerPropertyFunc2 getProp2,
	UINT32 index, PROPID propID, CComPropVariant &prop)
{
  if (getProp2)
	return getProp2(index, propID, &prop);;
  return getProp(propID, &prop);
}

static HRESULT ReadBoolProp(
	GetHandlerPropertyFunc getProp,
	GetHandlerPropertyFunc2 getProp2,
	UINT32 index, PROPID propID, bool &res)
{
  CComPropVariant prop;
  HRESULT hRes = ReadProp(getProp, getProp2, index, propID, prop);
  if(S_OK != hRes)
		return hRes;
  if (prop.vt == VT_BOOL)
	res = VARIANT_BOOLToBool(prop.boolVal);
  else if (prop.vt != VT_EMPTY)
	return E_FAIL;
  return S_OK;
}

static HRESULT ReadStringProp(
	GetHandlerPropertyFunc getProp,
	GetHandlerPropertyFunc2 getProp2,
	UINT32 index, PROPID propID, CString &res)
{
  CComPropVariant prop;
  HRESULT hRes = ReadProp(getProp, getProp2, index, propID, prop);
  if(S_OK != hRes)
		return hRes;
  if (prop.vt == VT_BSTR)
	res = prop.bstrVal;
  else if (prop.vt != VT_EMPTY)
	return E_FAIL;
  return S_OK;
}

#endif

static const unsigned int kNumArcsMax = 48;
static unsigned int g_NumArcs = 0;
static const CArcInfo *g_Arcs[kNumArcsMax];
void RegisterArc(const CArcInfo *arcInfo)
{
  if (g_NumArcs < kNumArcsMax)
	g_Arcs[g_NumArcs++] = arcInfo;
}

static void SplitString(const CString &srcString, CStringVector &destStrings)
{
  destStrings.clear();
  CString s;
  int len = srcString.GetLength();
  if (len == 0)
	return;
  for (int i = 0; i < len; i++)
  {
	wchar_t c = srcString[i];
	if (c == _T(' '))
	{
	  if (!s.IsEmpty())
	  {
		destStrings.push_back(s);
		s.Empty();
	  }
	}
	else
	  s += c;
  }
  if (!s.IsEmpty())
	destStrings.push_back(s);
}

void CArcInfoEx::AddExts(const wchar_t *ext, const wchar_t *addExt)
{
  CStringVector exts, addExts;
  if (ext != 0)
	SplitString(ext, exts);
  if (addExt != 0)
	SplitString(addExt, addExts);
  Exts.reserve(exts.size());
  for (size_t i = 0; i < exts.size(); i++)
  {
	CArcExtInfo extInfo;
	extInfo.Ext = exts[i];
	if (i < addExts.size())
	{
	  extInfo.AddExt = addExts[i];
	  if (extInfo.AddExt == _T("*"))
		extInfo.AddExt.Empty();
	}
	Exts.push_back(extInfo);
  }
}

#ifdef EXTERNAL_CODECS

HRESULT CCodecs::LoadFormats()
{
	if(Libs.empty())
		return S_OK;
  const DLL::CLibrary &lib = Libs.back().Lib;
  GetHandlerPropertyFunc getProp = 0;
  GetHandlerPropertyFunc2 getProp2 = (GetHandlerPropertyFunc2)lib.GetProcAddress("GetHandlerProperty2");
  if (getProp2 == NULL)
  {
	getProp = (GetHandlerPropertyFunc)lib.GetProcAddress("GetHandlerProperty");
	if (getProp == NULL)
	  return S_OK;
  }

  UINT32 numFormats = 1;
  GetNumberOfFormatsFunc getNumberOfFormats = (GetNumberOfFormatsFunc)lib.GetProcAddress("GetNumberOfFormats");
  HRESULT hRes (S_OK);
  if (getNumberOfFormats != NULL)
  {
	hRes = getNumberOfFormats(&numFormats);
	if(S_OK != hRes)
		return hRes;
  }
  if (getProp2 == NULL)
	numFormats = 1;

  Formats.reserve(numFormats);
  for(UINT32 i = 0; i < numFormats; i++)
  {
	CArcInfoEx item;
	item.LibIndex = Libs.size() - 1;
	item.FormatIndex = i;

	hRes = ReadStringProp(getProp, getProp2, i, static_cast<ULONG>(Common::Archive::Property::kName), item.Name);
	if(S_OK != hRes)
		return hRes;

	CComPropVariant prop;
	if (ReadProp(getProp, getProp2, i, static_cast<ULONG>(Common::Archive::Property::kClassID), prop) != S_OK)
	  continue;
	if (prop.vt != VT_BSTR)
	  continue;
	item.ClassID = *(const GUID *)prop.bstrVal;
	prop.Clear();

	CString ext, addExt;
	hRes = ReadStringProp(getProp, getProp2, i, static_cast<ULONG>(Common::Archive::Property::kExtension), ext);
	if(S_OK != hRes)
		return hRes;
	hRes = ReadStringProp(getProp, getProp2, i, static_cast<ULONG>(Common::Archive::Property::kAddExtension), addExt);
	if(S_OK != hRes)
		return hRes;
	item.AddExts(ext, addExt);

	ReadBoolProp(getProp, getProp2, i, static_cast<ULONG>(Common::Archive::Property::kUpdate), item.UpdateEnabled);
	if (item.UpdateEnabled)
	  ReadBoolProp(getProp, getProp2, i, static_cast<ULONG>(Common::Archive::Property::kKeepName), item.KeepName);
	
	if (ReadProp(getProp, getProp2, i, static_cast<ULONG>(Common::Archive::Property::kStartSignature), prop) == S_OK)
	  if (prop.vt == VT_BSTR)
	  {
		UINT len = ::SysStringByteLen(prop.bstrVal);
		//item.StartSignature.SetCapacity(len);
		item.StartSignature.resize(len);
		//memmove(&item.StartSignature[0], prop.bstrVal, len);
		item.StartSignature.assign((BYTE*)prop.bstrVal, ((BYTE*)prop.bstrVal) + len);
	  }
	  Formats.push_back(item);
  }
  return S_OK;
}

#ifdef NEW_FOLDER_INTERFACE
void CCodecIcons::LoadIcons(HMODULE m)
{
  CString iconTypes = String::LoadString(m, kIconTypesResId);
  CStringVector pairs;
  SplitString(iconTypes, pairs);
  IconPairs.reserve(pairs.size());
  for (size_t i = 0; i < pairs.size(); i++)
  {
	const CString &s = pairs[i];
	int pos = s.Find(_T(':'));
	CIconPair iconPair;
	iconPair.IconIndex = -1;
	if (pos < 0)
	  pos = s.GetLength();
	else
	{
	  CString num = s.Mid(pos + 1);
	  if (!num.IsEmpty())
	  {
		const wchar_t *end;
		iconPair.IconIndex = (UINT32)String::ConvertStringToUInt64(num, &end);
		if (*end != _T('\0'))
		  continue;
	  }
	}
	iconPair.Ext = s.Left(pos);
	IconPairs.push_back(iconPair);
  }
}

bool CCodecIcons::FindIconIndex(const CString &ext, int &iconIndex) const
{
  iconIndex = -1;
  for (size_t i = 0; i < IconPairs.size(); i++)
  {
	const CIconPair &pair = IconPairs[i];
	if (ext.CompareNoCase(pair.Ext) == 0)
	{
	  iconIndex = pair.IconIndex;
	  return true;
	}
  }
  return false;
}
#endif

#ifdef _7ZIP_LARGE_PAGES
extern "C"
{
  extern SIZE_T g_LargePageSize;
}
#endif

HRESULT CCodecs::LoadDll(const CString &dllPath, bool needCheckDll)
{
  if (needCheckDll)
  {
	DLL::CLibrary library;
	if (!library.LoadEx(dllPath, LOAD_LIBRARY_AS_DATAFILE))
	  return S_OK;
  }
  Libs.push_back(CCodecLib());
  CCodecLib &lib = Libs.back();
  #ifdef NEW_FOLDER_INTERFACE
  lib.Path = dllPath;
  #endif
  bool used = false;
  HRESULT res = S_OK;
  if (lib.Lib.Load(dllPath))
  {
	#ifdef NEW_FOLDER_INTERFACE
	lib.LoadIcons();
	#endif

	#ifdef _7ZIP_LARGE_PAGES
	if (g_LargePageSize != 0)
	{
	  SetLargePageModeFunc setLargePageMode = (SetLargePageModeFunc)lib.Lib.GetProc("SetLargePageMode");
	  if (setLargePageMode != 0)
		setLargePageMode();
	}
	#endif

	lib.CreateObject = (CreateObjectFunc)lib.Lib.GetProcAddress("CreateObject");
	if (lib.CreateObject != 0)
	{
	  size_t startSize = Codecs.size();
	  res = LoadCodecs();
	  used = (Codecs.size() != startSize);
	  if (res == S_OK)
	  {
		startSize = Formats.size();
		res = LoadFormats();
		used = used || (Formats.size() != startSize);
	  }
	}
  }
  if (!used)
	  Libs.pop_back();
  return res;
}

HRESULT CCodecs::LoadDllsFromFolder(const CString &folderPrefix)
{
  FileSystem::Find::CEnumerator enumerator(folderPrefix + CString(TEXT("*")));
  FileSystem::File::CFileInfo fi;
  HRESULT hRes(S_OK);
  while (enumerator.Next(&fi))
  {
	if (fi.IsDir())
	  continue;
	hRes = LoadDll(folderPrefix + fi.Name, true);
	if(S_OK != hRes)
		return hRes;
  }
  return S_OK;
}

#endif

#ifndef _SFX
static inline void SetBuffer(ByteBuffer &bb, const BYTE *data, int size)
{
	bb.resize(size);
	bb.assign(data, data + size);
  //memmove((BYTE *)&bb[0], data, size);
}
#endif

HRESULT CCodecs::Load()
{
  #ifdef NEW_FOLDER_INTERFACE
  InternalIcons.LoadIcons(_AtlBaseModule.GetModuleInstance());
  #endif

  Formats.clear();
  #ifdef EXTERNAL_CODECS
  Codecs.clear();
  #endif
  Formats.reserve(g_NumArcs);
  for (UINT32 i = 0; i < g_NumArcs; i++)
  {
	const CArcInfo &arc = *g_Arcs[i];
	CArcInfoEx item;
	item.Name = arc.Name;
	item.CreateInArchive = arc.CreateInArchive;
	item.CreateOutArchive = arc.CreateOutArchive;
	item.AddExts(arc.Ext, arc.AddExt);
	item.UpdateEnabled = (arc.CreateOutArchive != 0);
	item.KeepName = arc.KeepName;

	#ifndef _SFX
	SetBuffer(item.StartSignature, arc.Signature, arc.SignatureSize);
	#endif
	Formats.push_back(item);
  }
  #ifdef EXTERNAL_CODECS
  const CString baseFolder = GetBaseFolderPrefixFromRegistry();
  HRESULT hRes = LoadDll(baseFolder + kMainDll, false);
  if(S_OK != hRes)
	return hRes;
  hRes = LoadDllsFromFolder(baseFolder + kCodecsFolderName STRING_PATH_SEPARATOR);
  if(S_OK != hRes)
	return hRes;
  hRes = LoadDllsFromFolder(baseFolder + kFormatsFolderName STRING_PATH_SEPARATOR);
  if(S_OK != hRes)
	return hRes;
  #endif
  return S_OK;
}

#ifndef _SFX

size_t CCodecs::FindFormatForArchiveName(const CString &arcPath) const
{
  int slashPos1 = arcPath.ReverseFind(CHAR_PATH_SEPARATOR);
  int slashPos2 = arcPath.ReverseFind(_T('.'));
  int dotPos = arcPath.ReverseFind(_T('.'));
  if (dotPos < 0 || dotPos < slashPos1 || dotPos < slashPos2)
	return -1;
  CString ext = arcPath.Mid(dotPos + 1);
  for (size_t i = 0; i < Formats.size(); i++)
  {
	const CArcInfoEx &arc = Formats[i];
	if (!arc.UpdateEnabled)
	  continue;
	if (arc.FindExtension(ext) != -1)
	  return i;
  }
  return -1;
}

size_t CCodecs::FindFormatForExtension(const CString &ext) const
{
  if (ext.IsEmpty())
	return -1;
  for (size_t i = 0; i < Formats.size(); i++)
	if (Formats[i].FindExtension(ext) != -1)
	  return i;
  return -1;
}

size_t CCodecs::FindFormatForArchiveType(const CString &arcType) const
{
  for (size_t i = 0; i < Formats.size(); i++)
	if (Formats[i].Name.CompareNoCase(arcType) == 0)
	  return i;
  return -1;
}

bool CCodecs::FindFormatForArchiveType(const CString &arcType, std::vector<int> &formatIndices) const
{
  formatIndices.clear();
  formatIndices.reserve(arcType.GetLength());
  for (int pos = 0; pos < arcType.GetLength();)
  {
	int pos2 = arcType.Find(_T('.'), pos);
	if (pos2 < 0)
	  pos2 = arcType.GetLength();
	const CString name = arcType.Mid(pos, pos2 - pos);
	size_t index = FindFormatForArchiveType(name);
	if (index == -1 && name != _T("*"))
	{
	  formatIndices.clear();
	  return false;
	}
	formatIndices.push_back(index);
	pos = pos2 + 1;
  }
  return true;
}

#endif

#ifdef EXTERNAL_CODECS

#ifdef EXPORT_CODECS
extern unsigned int g_NumCodecs;
STDAPI CreateCoder2(bool encode, UINT32 index, const GUID *iid, void **outObject);
STDAPI GetMethodProperty(UINT32 codecIndex, PROPID propID, PROPVARIANT *value);
// STDAPI GetNumberOfMethods(UINT32 *numCodecs);
#endif

STDMETHODIMP CCodecs::GetNumberOfMethods(UINT32 *numMethods)
{
  *numMethods =
	  #ifdef EXPORT_CODECS
	  g_NumCodecs +
	  #endif
	  Codecs.size();
  return S_OK;
}

STDMETHODIMP CCodecs::GetProperty(UINT32 index, PROPID propID, PROPVARIANT *value)
{
  #ifdef EXPORT_CODECS
  if (index < g_NumCodecs)
	return GetMethodProperty(index, propID, value);
  #endif

  const CDllCodecInfo &ci = Codecs[index
	  #ifdef EXPORT_CODECS
	  - g_NumCodecs
	  #endif
	  ];

  if (propID == MethodPropID::kDecoderIsAssigned)
  {
	CComPropVariant propVariant;
	propVariant = ci.DecoderIsAssigned;
	propVariant.Detach(value);
	return S_OK;
  }
  if (propID == MethodPropID::kEncoderIsAssigned)
  {
	CComPropVariant propVariant;
	propVariant = ci.EncoderIsAssigned;
	propVariant.Detach(value);
	return S_OK;
  }
  return Libs[ci.LibIndex].GetMethodProperty(ci.CodecIndex, propID, value);
}

STDMETHODIMP CCodecs::CreateDecoder(UINT32 index, const GUID *iid, void **coder)
{
  #ifdef EXPORT_CODECS
  if (index < g_NumCodecs)
	return CreateCoder2(false, index, iid, coder);
  #endif
  const CDllCodecInfo &ci = Codecs[index
	  #ifdef EXPORT_CODECS
	  - g_NumCodecs
	  #endif
	  ];
  if (ci.DecoderIsAssigned)
	return Libs[ci.LibIndex].CreateObject(&ci.Decoder, iid, (void **)coder);
  return S_OK;
}

STDMETHODIMP CCodecs::CreateEncoder(UINT32 index, const GUID *iid, void **coder)
{
  #ifdef EXPORT_CODECS
  if (index < g_NumCodecs)
	return CreateCoder2(true, index, iid, coder);
  #endif
  const CDllCodecInfo &ci = Codecs[index
	  #ifdef EXPORT_CODECS
	  - g_NumCodecs
	  #endif
	  ];
  if (ci.EncoderIsAssigned)
	return Libs[ci.LibIndex].CreateObject(&ci.Encoder, iid, (void **)coder);
  return S_OK;
}

HRESULT CCodecs::CreateCoder(const CString &name, bool encode, ICompressCoder** coder) const
{
  HRESULT hRes (S_OK);
  for (size_t i = 0; i < Codecs.size(); i++)
  {
	const CDllCodecInfo &codec = Codecs[i];
	if (encode && !codec.EncoderIsAssigned || !encode && !codec.DecoderIsAssigned)
	  continue;
	const CCodecLib &lib = Libs[codec.LibIndex];
	CString res;
	CComPropVariant prop;
	hRes = lib.GetMethodProperty(codec.CodecIndex, MethodPropID::kName, &prop);
	if(S_OK != hRes)
		return hRes;
	if (prop.vt == VT_BSTR)
	  res = prop.bstrVal;
	else if (prop.vt != VT_EMPTY)
	  continue;
	if (name.CompareNoCase(res) == 0)      
	  return lib.CreateObject(encode ? &codec.Encoder : &codec.Decoder, &__uuidof(ICompressCoder), (void **)coder);
  }
  return CLASS_E_CLASSNOTAVAILABLE;
}

int CCodecs::GetCodecLibIndex(UINT32 index)
{
  #ifdef EXPORT_CODECS
  if (index < g_NumCodecs)
	return -1;
  #endif
  #ifdef EXTERNAL_CODECS
  const CDllCodecInfo &ci = Codecs[index
	  #ifdef EXPORT_CODECS
	  - g_NumCodecs
	  #endif
	  ];
  return ci.LibIndex;
  #else
  return -1;
  #endif
}

bool CCodecs::GetCodecEncoderIsAssigned(UINT32 index)
{
  #ifdef EXPORT_CODECS
  if (index < g_NumCodecs)
  {
	CComPropVariant prop;
	if (GetProperty(index, MethodPropID::kEncoder, &prop) == S_OK)
	  if (prop.vt != VT_EMPTY)
		return true;
	return false;
  }
  #endif
  #ifdef EXTERNAL_CODECS
  const CDllCodecInfo &ci = Codecs[index
	  #ifdef EXPORT_CODECS
	  - g_NumCodecs
	  #endif
	  ];
  return ci.EncoderIsAssigned;
  #else
  return false;
  #endif
}

HRESULT CCodecs::GetCodecId(UINT32 index, UINT64 &id)
{
  CComPropVariant prop;
  HRESULT hRes = GetProperty(index, MethodPropID::kID, &prop);
  if(S_OK != hRes)
	return hRes;
  if (prop.vt != VT_UI8)
	return E_INVALIDARG;
  id = prop.uhVal.QuadPart;
  return S_OK;
}

CString CCodecs::GetCodecName(UINT32 index)
{
  CString s;
  CComPropVariant prop;
  if (GetProperty(index, MethodPropID::kName, &prop) == S_OK)
	if (prop.vt == VT_BSTR)
	  s = prop.bstrVal;
  return s;
}


#endif
}
}